--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Commit 7076fae5cc554d603dbcd1925eee80a119e2dc13
Parents : c1b5b77
Author : Mark Qvist <mark@unsigned.io>
Date : 2023-10-30T02:28:35+01:00
Added command handling and sending
Changes
4 files changed, 171 insertions(+), 24 deletions(-)
Diff
diff --git a/sbapp/main.py b/sbapp/main.py
index 81042605..1ecb11b2 100644
--- a/sbapp/main.py
+++ b/sbapp/main.py
@@ -829,7 +829,9 @@ class SidebandApp(MDApp):
self.message_send_action()
if text == "l":
- if self.root.ids.screen_manager.current == "map_screen":
+ if self.root.ids.screen_manager.current == "messages_screen":
+ self.message_propagation_action(self)
+ elif self.root.ids.screen_manager.current == "map_screen":
self.map_layers_action()
else:
self.announces_action(self)
@@ -1006,6 +1008,7 @@ class SidebandApp(MDApp):
def open_conversation(self, context_dest, direction="left"):
self.outbound_mode_paper = False
+ self.outbound_mode_command = False
if self.sideband.config["propagation_by_default"]:
self.outbound_mode_propagation = True
else:
@@ -1075,7 +1078,26 @@ class SidebandApp(MDApp):
else:
msg_content = self.messages_view.ids.message_text.text
context_dest = self.messages_view.ids.messages_scrollview.active_conversation
- if self.outbound_mode_paper:
+ if self.outbound_mode_command:
+ if self.sideband.send_command(msg_content, context_dest, False):
+ self.messages_view.ids.message_text.text = ""
+ self.messages_view.ids.messages_scrollview.scroll_y = 0
+ self.jobs(0)
+ else:
+ self.messages_view.send_error_dialog = MDDialog(
+ title="Error",
+ text="Could not send the command. Check that the syntax is correct, and that the command is supported.",
+ buttons=[
+ MDRectangleFlatButton(
+ text="OK",
+ font_size=dp(18),
+ on_release=self.messages_view.close_send_error_dialog
+ )
+ ],
+ )
+ self.messages_view.send_error_dialog.open()
+
+ elif self.outbound_mode_paper:
if self.sideband.paper_message(msg_content, context_dest):
self.messages_view.ids.message_text.text = ""
self.messages_view.ids.messages_scrollview.scroll_y = 0
@@ -1117,16 +1139,24 @@ class SidebandApp(MDApp):
self.object_details_action(self.messages_view, from_conv=True)
def message_propagation_action(self, sender):
- if self.outbound_mode_paper:
+ if self.outbound_mode_command:
self.outbound_mode_paper = False
self.outbound_mode_propagation = False
+ self.outbound_mode_command = False
else:
- if self.outbound_mode_propagation:
- self.outbound_mode_paper = True
+ if self.outbound_mode_paper:
+ self.outbound_mode_paper = False
self.outbound_mode_propagation = False
+ self.outbound_mode_command = True
else:
- self.outbound_mode_propagation = True
- self.outbound_mode_paper = False
+ if self.outbound_mode_propagation:
+ self.outbound_mode_paper = True
+ self.outbound_mode_propagation = False
+ self.outbound_mode_command = False
+ else:
+ self.outbound_mode_propagation = True
+ self.outbound_mode_paper = False
+ self.outbound_mode_command = False
self.update_message_widgets()
@@ -1138,13 +1168,17 @@ class SidebandApp(MDApp):
mode_item.icon = "qrcode"
self.messages_view.ids.message_text.hint_text = "Paper message"
else:
- if not self.outbound_mode_propagation:
- mode_item.icon = "lan-connect"
- self.messages_view.ids.message_text.hint_text = "Message for direct delivery"
+ if self.outbound_mode_command:
+ mode_item.icon = "console"
+ self.messages_view.ids.message_text.hint_text = "Send command or request"
else:
- mode_item.icon = "upload-network"
- self.messages_view.ids.message_text.hint_text = "Message for propagation"
- # self.root.ids.message_text.hint_text = "Write message for delivery via propagation nodes"
+ if not self.outbound_mode_propagation:
+ mode_item.icon = "lan-connect"
+ self.messages_view.ids.message_text.hint_text = "Message for direct delivery"
+ else:
+ mode_item.icon = "upload-network"
+ self.messages_view.ids.message_text.hint_text = "Message for propagation"
+ # self.root.ids.message_text.hint_text = "Write message for delivery via propagation nodes"
def key_query_action(self, sender):
context_dest = self.messages_view.ids.messages_scrollview.active_conversation
diff --git a/sbapp/sideband/core.py b/sbapp/sideband/core.py
index 6fd4a0ed..cf09b341 100644
--- a/sbapp/sideband/core.py
+++ b/sbapp/sideband/core.py
@@ -1976,7 +1976,7 @@ class SidebandCore():
messages = messages[-limit:]
return messages
- def _db_save_lxm(self, lxm, context_dest, originator = False):
+ def _db_save_lxm(self, lxm, context_dest, originator = False, own_command = False):
state = lxm.state
packed_telemetry = None
@@ -1998,7 +1998,7 @@ class SidebandCore():
# TODO: Implement
pass
- if len(lxm.content) != 0 or len(lxm.title) != 0:
+ if own_command or len(lxm.content) != 0 or len(lxm.title) != 0:
db = self.__db_connect()
dbc = db.cursor()
@@ -2977,7 +2977,7 @@ class SidebandCore():
RNS.log("Error while creating paper message: "+str(e), RNS.LOG_ERROR)
return False
- def send_message(self, content, destination_hash, propagation):
+ def send_message(self, content, destination_hash, propagation, skip_fields=False):
try:
if content == "":
raise ValueError("Message content cannot be empty")
@@ -2991,7 +2991,54 @@ class SidebandCore():
else:
desired_method = LXMF.LXMessage.DIRECT
- lxm = LXMF.LXMessage(dest, source, content, title="", desired_method=desired_method, fields = self.get_message_fields(destination_hash))
+ if skip_fields:
+ fields = {}
+ else:
+ fields = self.get_message_fields(destination_hash)
+
+ lxm = LXMF.LXMessage(dest, source, content, title="", desired_method=desired_method, fields = fields)
+ lxm.register_delivery_callback(self.message_notification)
+ lxm.register_failed_callback(self.message_notification)
+
+ if self.message_router.get_outbound_propagation_node() != None:
+ if self.config["lxmf_try_propagation_on_fail"]:
+ lxm.try_propagation_on_fail = True
+
+ self.message_router.handle_outbound(lxm)
+ self.lxm_ingest(lxm, originator=True)
+
+ return True
+
+ except Exception as e:
+ RNS.log("Error while sending message: "+str(e), RNS.LOG_ERROR)
+ return False
+
+ def send_command(self, content, destination_hash, propagation):
+ try:
+ if content == "":
+ return False
+
+ commands = []
+ if content.startswith("echo "):
+ echo_content = content.replace("echo ", "").encode("utf-8")
+ if len(echo_content) > 0:
+ commands.append({Commands.ECHO: echo_content})
+ elif content.startswith("sig"):
+ commands.append({Commands.SIGNAL_REPORT: True})
+
+ if len(commands) == 0:
+ return False
+
+ dest_identity = RNS.Identity.recall(destination_hash)
+ dest = RNS.Destination(dest_identity, RNS.Destination.OUT, RNS.Destination.SINGLE, "lxmf", "delivery")
+ source = self.lxmf_destination
+
+ if propagation:
+ desired_method = LXMF.LXMessage.PROPAGATED
+ else:
+ desired_method = LXMF.LXMessage.DIRECT
+
+ lxm = LXMF.LXMessage(dest, source, "", title="", desired_method=desired_method, fields = {LXMF.FIELD_COMMANDS: commands})
lxm.register_delivery_callback(self.message_notification)
lxm.register_failed_callback(self.message_notification)
@@ -3060,6 +3107,7 @@ class SidebandCore():
should_notify = False
is_trusted = False
telemetry_only = False
+ own_command = False
unread_reason_tx = False
if originator:
@@ -3069,18 +3117,21 @@ class SidebandCore():
context_dest = message.source_hash
is_trusted = self.is_trusted(context_dest)
+ if originator and LXMF.FIELD_COMMANDS in message.fields:
+ own_command = True
+
if self._db_message(message.hash):
RNS.log("Message exists, setting state to: "+str(message.state), RNS.LOG_DEBUG)
self._db_message_set_state(message.hash, message.state)
else:
RNS.log("Message does not exist, saving", RNS.LOG_DEBUG)
- self._db_save_lxm(message, context_dest, originator)
+ self._db_save_lxm(message, context_dest, originator, own_command=own_command)
if is_trusted:
should_notify = True
if len(message.content) == 0 and len(message.title) == 0:
- if (LXMF.FIELD_TELEMETRY in message.fields or LXMF.FIELD_TELEMETRY_STREAM in message.fields):
+ if (LXMF.FIELD_TELEMETRY in message.fields or LXMF.FIELD_TELEMETRY_STREAM in message.fields or LXMF.FIELD_COMMANDS in message.fields):
RNS.log("Squelching notification due to telemetry-only message", RNS.LOG_DEBUG)
telemetry_only = True
@@ -3236,16 +3287,57 @@ class SidebandCore():
RNS.log("LXMF delivery "+str(time_string)+". "+str(signature_string)+".", RNS.LOG_DEBUG)
try:
+ context_dest = message.source_hash
if self.config["lxmf_ignore_unknown"] == True:
- context_dest = message.source_hash
if self._db_conversation(context_dest) == None:
RNS.log("Dropping message from unknown sender "+RNS.prettyhexrep(context_dest), RNS.LOG_DEBUG)
return
- self.lxm_ingest(message)
+ if message.signature_validated and LXMF.FIELD_COMMANDS in message.fields:
+ if self.requests_allowed_from(context_dest):
+ commands = message.fields[LXMF.FIELD_COMMANDS]
+ self.handle_commands(commands, message)
+
+ else:
+ self.lxm_ingest(message)
+
except Exception as e:
RNS.log("Error while ingesting LXMF message "+RNS.prettyhexrep(message.hash)+" to database: "+str(e))
+ def handle_commands(self, commands, message):
+ try:
+ context_dest = message.source_hash
+ RNS.log("Handling commands from "+str(context_dest), RNS.LOG_DEBUG)
+ for command in commands:
+ if Commands.TELEMETRY_REQUEST in command:
+ timebase = int(command[Commands.TELEMETRY_REQUEST])
+ RNS.log("Handling telemetry request with timebase "+str(timebase), RNS.LOG_DEBUG)
+
+ elif Commands.ECHO in command:
+ msg_content = "Echo reply: "+command[Commands.ECHO].decode("utf-8")
+ RNS.log("Handling echo request", RNS.LOG_DEBUG)
+ self.send_message(msg_content, context_dest, False, skip_fields=True)
+
+ elif Commands.SIGNAL_REPORT in command:
+ RNS.log("Handling signal report", RNS.LOG_DEBUG)
+ phy_str = ""
+ if message.q != None:
+ phy_str += f"Link Quality: {message.q}%\n"
+ if message.rssi != None:
+ phy_str += f"RSSI: {message.rssi} dBm\n"
+ if message.snr != None:
+ phy_str += f"SNR: {message.rssi} dB\n"
+ if len(phy_str) != 0:
+ phy_str = phy_str[:-1]
+ else:
+ phy_str = "No reception info available"
+
+ self.send_message(phy_str, context_dest, False, skip_fields=True)
+
+ except Exception as e:
+ RNS.log("Error while handling commands: "+str(e), RNS.LOG_ERROR)
+
+
def get_display_name_bytes(self):
return self.config["display_name"].encode("utf-8")
diff --git a/sbapp/sideband/sense.py b/sbapp/sideband/sense.py
index 431c919f..33f78871 100644
--- a/sbapp/sideband/sense.py
+++ b/sbapp/sideband/sense.py
@@ -10,6 +10,8 @@ from .geo import azalt, angle_to_horizon, radio_horizon, shared_radio_horizon
class Commands():
TELEMETRY_REQUEST = 0x01
+ ECHO = 0x02
+ SIGNAL_REPORT = 0x03
class Telemeter():
@staticmethod
diff --git a/sbapp/ui/messages.py b/sbapp/ui/messages.py
index d9b9f38f..7d969d7a 100644
--- a/sbapp/ui/messages.py
+++ b/sbapp/ui/messages.py
@@ -22,11 +22,11 @@ import subprocess
import shlex
if RNS.vendor.platformutils.get_platform() == "android":
- from sideband.sense import Telemeter
+ from sideband.sense import Telemeter, Commands
from ui.helpers import ts_format, file_ts_format, mdc
from ui.helpers import color_received, color_delivered, color_propagated, color_paper, color_failed, color_unknown, intensity_msgs_dark, intensity_msgs_light
else:
- from sbapp.sideband.sense import Telemeter
+ from sbapp.sideband.sense import Telemeter, Commands
from .helpers import ts_format, file_ts_format, mdc
from .helpers import color_received, color_delivered, color_propagated, color_paper, color_failed, color_unknown, intensity_msgs_dark, intensity_msgs_light
@@ -178,14 +178,29 @@ class Messages():
txstr = time.strftime(ts_format, time.localtime(m["sent"]))
rxstr = time.strftime(ts_format, time.localtime(m["received"]))
titlestr = ""
+ extra_content = ""
extra_telemetry = {}
telemeter = None
+ signature_valid = False
+
+ if "lxm" in m and m["lxm"] != None and m["lxm"].signature_validated:
+ signature_valid = True
+
if "extras" in m and m["extras"] != None and "packed_telemetry" in m["extras"]:
try:
telemeter = Telemeter.from_packed(m["extras"]["packed_telemetry"])
except Exception as e:
pass
+ if "lxm" in m and m["lxm"] != None and m["lxm"].fields != None and LXMF.FIELD_COMMANDS in m["lxm"].fields:
+ commands = m["lxm"].fields[LXMF.FIELD_COMMANDS]
+ for command in commands:
+ if Commands.ECHO in command:
+ extra_content = "[font=RobotoMono-Regular]> echo "+command[Commands.ECHO].decode("utf-8")+"[/font]\n"
+ if Commands.SIGNAL_REPORT in command:
+ extra_content = "[font=RobotoMono-Regular]> sig[/font]\n"
+ extra_content = extra_content[:-1]
+
if telemeter == None and "lxm" in m and m["lxm"] and m["lxm"].fields != None and LXMF.FIELD_TELEMETRY in m["lxm"].fields:
try:
packed_telemetry = m["lxm"].fields[LXMF.FIELD_TELEMETRY]
@@ -272,8 +287,12 @@ class Messages():
if rcvd_d_str != "":
heading_str += rcvd_d_str
+ pre_content = ""
+ if not signature_valid:
+ pre_content += "[b]Warning![/b] The signature for this message could not be validated. Check that you have received an announce from this sender. If you already have, or other messages from the sender do not display this warning, [b]this message is likely to be fake[/b].\n\n"
+
item = ListLXMessageCard(
- text=m["content"].decode("utf-8"),
+ text=pre_content+m["content"].decode("utf-8")+extra_content,
heading=heading_str,
md_bg_color=msg_color,
)
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────